import datetime
import copy
import pymongo
import Core.Gadget as Gadget
import Core.Algorithm as Algo
import Core.IO as IO
from Factors.General import *
import Core.DataSeries as DataSerie
import numpy as np


def GreenblattROCCallback(dfFundamental, factorName, fields, params):
    #
    # print(dfFundamental.head(10))
    type = "_" + params["Type"]
    if "LYR" in type:
        dfFundamental[factorName] = dfFundamental["EBIT" + type] / \
                                    (dfFundamental["WorkingCapital" + type] - dfFundamental["CashEquivalents" + type] + dfFundamental["PPE" + type])
    elif "TTM" in type:
        dfFundamental[factorName] = dfFundamental["EBIT" + type] / \
                                    (dfFundamental["WorkingCapital"] - dfFundamental["CashEquivalents"] + dfFundamental["PPE"])


def GrossProfitOnAssetCallback(dfFundamental, factorName, fields, params):
    #
    type = "_" + params["Type"]
    if "LYR" in type:
        dfFundamental[factorName] = dfFundamental["GrossProfit" + type] / dfFundamental["TotalAsset" + type]
    elif "TTM" in type:
        dfFundamental[factorName] = dfFundamental["GrossProfit" + type] / dfFundamental["TotalAsset"]


def CalcProfitabilityFactor(database, instruments, datetime1, datetime2, update=False):

    # ---Profit Margin 不区分 TTM LYR 的因子---
    for earning in ["OperatingProfit1", "OperatingProfit", "NetIncome2"]:
        CalcFundamentalFactor(database, instruments, datetime1, datetime2,
                              factorName="ProfitMargin_" + earning,
                              fields=[],
                              quarterlyFields=["Sales", earning],
                              callback=ConsensusDivideCallback,
                              update=update,
                              params={"Numerator": earning, "Denominator": "Sales"})


    for type in ["TTM", "LYR"]:
        #
        for earning in ["OperatingProfit1", "OperatingProfit", "NetIncome2"]:
            # Profit Margin
            CalcFundamentalFactor(database, instruments, datetime1, datetime2,
                                  factorName="ProfitMargin_" + earning + "_" + type,
                                  fields=[],
                                  quarterlyFields=["Sales", earning],
                                  callback=ConsensusDivideCallback,
                                  update=update,
                                  params={"Numerator": earning, "Denominator": "Sales", "Type": type})
            # ROA
            CalcFundamentalFactor(database, instruments, datetime1, datetime2,
                                  factorName="ROA_" + earning + "_" + type,
                                  fields=["TotalAsset"],
                                  quarterlyFields=[earning],
                                  callback=DivideCallback,
                                  update=update,
                                  params={"Numerator": earning, "Denominator": "TotalAsset", "Type": type})
            # ROE
            CalcFundamentalFactor(database, instruments, datetime1, datetime2,
                                  factorName="ROE_" + earning + "_" + type,
                                  fields=["TotalEquity"],
                                  quarterlyFields=[earning],
                                  callback=DivideCallback,
                                  update=update,
                                  params={"Numerator": earning, "Denominator": "TotalEquity", "Type": type})

        # ---Ok---
        #
        #CalcFundamentalFactor(database, instruments, datetime1, datetime2,
        #                      factorName="FCFToAsset" + type,
        #                      fields=["TotalAsset"],
        #                      quarterlyFields=["FreeCashFlow"],
        #                      callback=DivideCallback,
        #                      params={"Numerator": "FreeCashFlow", "Denominator": "TotalAsset", "Type": type})
        #
        #CalcFundamentalFactor(database, instruments, datetime1, datetime2,
        #                      factorName="GreenblattROC" + type,
        #                      fields=["WorkingCapital","CashEquivalents","PPE"],
        #                      quarterlyFields=["EBIT"],
        #                      callback=GreenblattROCCallback,
        #                      params={"Type": type})
        #CalcFundamentalFactor(database, instruments, datetime1, datetime2,
        #                      factorName="GrossProfitOnAsset" + type,
        #                      fields=["TotalAsset"],
        #                      quarterlyFields=["Sales","GrossProfit"],
        #                      callback=GrossProfitOnAssetCallback,
        #                      params={"Type": type})
        #
        #CalcFundamentalFactor(database, instruments, datetime1, datetime2,
        #                      factorName="GrossProfitMargin" + type,
        #                      fields=[],
        #                      quarterlyFields=["Sales","GrossProfit"],
        #                      callback=BothQuarterlyDivideCallback,
        #                      params={"Numerator": "GrossProfit", "Denominator": "Sales", "Type": type})


def CalcProfitabilityFactorOld(database, instruments, datetime1, datetime2):
    #
    fundamentalsBySymbol = {}
    #
    count = 0
    for instrument in instruments:
        symbol = instrument["Symbol"]
        count = count + 1

        # ---Create DataSeries---
        # ---LYR (Static) DataSeries---
        dataSeries_ROA_LYR = DataSeries.DataSeries(symbol + "_ROALYR_Factor")
        dataSeries_ROE_LYR = DataSeries.DataSeries(symbol + "_ROELYR_Factor")
        dataSeries_GreenblattROC_LYR = DataSeries.DataSeries(symbol + "_GreenblattROCLYR_Factor")
        dataSeries_GPA_LYR = DataSeries.DataSeries(symbol + "_GrossProfitAssetLYR_Factor")
        dataSeries_GrossMargin_LYR = DataSeries.DataSeries(symbol + "_GrossProfitMarginLYR_Factor")
        dataSeries_FCFAsset_LYR = DataSeries.DataSeries(symbol + "_FreeCashFlowToAssetLYR_Factor")
        #
        dataSeries_Factor_LYR_s = []
        dataSeries_Factor_LYR_s.append(dataSeries_ROA_LYR)
        dataSeries_Factor_LYR_s.append(dataSeries_ROE_LYR)
        dataSeries_Factor_LYR_s.append(dataSeries_GreenblattROC_LYR)
        dataSeries_Factor_LYR_s.append(dataSeries_GPA_LYR)
        dataSeries_Factor_LYR_s.append(dataSeries_GrossMargin_LYR)
        dataSeries_Factor_LYR_s.append(dataSeries_FCFAsset_LYR)

        # --- TTM Rolling ---
        dataSeries_ROA_TTM = DataSeries.DataSeries(symbol + "_ROATTM_Factor")
        dataSeries_ROE_TTM = DataSeries.DataSeries(symbol + "_ROETTM_Factor")
        dataSeries_GreenblattROC_TTM = DataSeries.DataSeries(symbol + "_GreenblattROCTTM_Factor")
        dataSeries_GPA_TTM = DataSeries.DataSeries(symbol + "_GrossProfitAssetTTM_Factor")
        dataSeries_GrossMargin_TTM = DataSeries.DataSeries(symbol + "_GrossProfitMarginTTM_Factor")
        dataSeries_FCFAsset_TTM = DataSeries.DataSeries(symbol + "_FreeCashFlowToAssetTTM_Factor")
        #
        dataSeries_Factor_TTM_s = []
        dataSeries_Factor_TTM_s.append(dataSeries_ROA_TTM)
        dataSeries_Factor_TTM_s.append(dataSeries_ROE_TTM)
        dataSeries_Factor_TTM_s.append(dataSeries_GreenblattROC_TTM)
        dataSeries_Factor_TTM_s.append(dataSeries_GPA_TTM)
        dataSeries_Factor_TTM_s.append(dataSeries_GrossMargin_TTM)
        dataSeries_Factor_LYR_s.append(dataSeries_FCFAsset_TTM)

        # --- Load (Total) Fundamental Data ---
        if symbol not in fundamentalsBySymbol:
            # statements = database.findWithFilter("Fundamental", symbol + "_Fundamental", filter, sort)
            statements = database.getDataSeries(symbol + "_Fundamental")
            fundamentalsBySymbol[symbol] = statements

        # --- Load Quotes ---
        quotes = database.getDataSeries(symbol + "_Time_86400_Bar", "Stock", datetime1, datetime2)

        # ---Calculation Statements---
        # ---Last File---
        dataSeries_TotalEquity_LF = CalculateLF(database, instrument, datetime1, datetime2, "TotalEquity", fundamentalsBySymbol)
        dataSeries_TotalAsset_LF = CalculateLF(database, instrument, datetime1, datetime2, "TotalAsset", fundamentalsBySymbol)
        dataSeries_PPE_LF = CalculateLF(database, instrument, datetime1, datetime2, "PPE", fundamentalsBySymbol)
        dataSeries_WorkingCapital_LF = CalculateLF(database, instrument, datetime1, datetime2, "WorkingCapital", fundamentalsBySymbol)
        dataSeries_CashEquivalents_LF = CalculateLF(database, instrument, datetime1, datetime2, "CashEquivalents", fundamentalsBySymbol)

        # ---LYR---
        dataSeries_OperatingProfit1_LYR = CalculateLYR(database, instrument, datetime1, datetime2, "OperatingProfit1", fundamentalsBySymbol)
        dataSeries_EBIT_LYR = CalculateLYR(database, instrument, datetime1, datetime2, "EBIT", fundamentalsBySymbol)
        dataSeries_Sales_LYR = CalculateLYR(database, instrument, datetime1, datetime2, "Sales", fundamentalsBySymbol)
        dataSeries_COGS_LYR = CalculateLYR(database, instrument, datetime1, datetime2, "COGS", fundamentalsBySymbol)
        dataSeries_FreeCashFlow_LYR = CalculateLYR(database, instrument, datetime1, datetime2, "FreeCashFlow", fundamentalsBySymbol)
        #

        # ---TTM---
        dataSeries_OperatingProfit1_TTM = CalculateTTM(database, instrument, datetime1, datetime2, "OperatingProfit1", fundamentalsBySymbol)
        dataSeries_EBIT_TTM = CalculateTTM(database, instrument, datetime1, datetime2, "EBIT", fundamentalsBySymbol)
        dataSeries_Sales_TTM = CalculateTTM(database, instrument, datetime1, datetime2, "Sales", fundamentalsBySymbol)
        dataSeries_COGS_TTM = CalculateTTM(database, instrument, datetime1, datetime2, "COGS", fundamentalsBySymbol)
        dataSeries_FreeCashFlow_TTM = CalculateTTM(database, instrument, datetime1, datetime2, "FreeCashFlow", fundamentalsBySymbol)
        #

        # ---DateTime Range ---
        minimalDT = datetime.datetime(1980, 1, 1)
        minimalDT = Gadget.ToUTCDateTime(minimalDT)
        #
        ipoDT = instrument["IPO_Date"]
        ipoDT = Gadget.ToUTCDateTime(ipoDT)
        #
        beginDT = datetime1
        if ipoDT > datetime1:
            beginDT = ipoDT

        # ---
        datetimes = Gadget.GenerateMonthDates(beginDT, datetime2)

        # --- Loop Dates (Beginning day of Month) ---
        for dt in datetimes:
            # print("### " + str(dt) + " ###")

            # ---Finds the nearest quote and statement---
            quote = quotes.Get(dt, "Previous")
            if quote is None:
                print(symbol + " No Quote @BacktestDate " + str(dt))
                continue

            # ---Base Ducument ---
            document = {}
            document["Symbol"] = symbol
            document["StdDateTime"] = quote["StdDateTime"]

            #
            try:

                # ---Calculation Factor ---
                # ---Prepare Last File ---
                totalEquity_LF = dataSeries_TotalEquity_LF.Get(dt)
                totalAsset_LF = dataSeries_TotalAsset_LF.Get(dt)
                ppe_LF = dataSeries_PPE_LF.Get(dt)
                cashEquivalents_LF = dataSeries_CashEquivalents_LF.Get(dt)
                workingCapital_LF = dataSeries_WorkingCapital_LF(dt)

                # ---Prepare LYR ---
                operatingProfit_LYR = dataSeries_OperatingProfit1_LYR.Get(dt)
                ebit_LYR = dataSeries_EBIT_LYR.Get(dt)
                sales_LYR = dataSeries_Sales_LYR.Get(dt)
                cogs_LYR = dataSeries_COGS_LYR.Get(dt)
                freeCadhFlow_LYR = dataSeries_FreeCashFlow_LYR.Get(dt)

                # --- Prepare TTM ---
                operatingProfit_TTM = dataSeries_OperatingProfit1_TTM.Get(dt)
                ebit_TTM = dataSeries_EBIT_TTM.Get(dt)
                sales_TTM = dataSeries_Sales_TTM.Get(dt)
                cogs_TTM = dataSeries_COGS_TTM.Get(dt)
                freeCadhFlow_TTM = dataSeries_FreeCashFlow_TTM.Get(dt)



                # ---Return on Asset
                if totalAsset_LF != None and operatingProfit_LYR != None:
                    doc_ROA_LYR = copy.deepcopy(document)
                    doc_ROA_LYR["Value"] = operatingProfit_LYR["Value"] / totalAsset_LF["Value"]
                    dataSeries_ROA_LYR.Add(doc_ROA_LYR)
                    # print(str(quote["StdDateTime"]) + " " + str(priceBookDoc_LF["Value"]))

                if totalAsset_LF != None and operatingProfit_TTM != None:
                    doc_ROA_TTM = copy.deepcopy(document)
                    doc_ROA_TTM["Value"] = operatingProfit_TTM["Value"] / totalAsset_LF["Value"]
                    dataSeries_ROA_TTM.Add(doc_ROA_TTM)
                    # print(str(quote["StdDateTime"]) + " " + str(priceBookDoc_LF["Value"]))

                # --- Return on Equity ---
                if totalEquity_LF != None and operatingProfit_LYR != None:
                    doc_ROE_LYR = copy.deepcopy(document)
                    doc_ROE_LYR["Value"] = operatingProfit_LYR["Value"] / totalEquity_LF["Value"]
                    dataSeries_ROE_LYR.Add(doc_ROE_LYR)
                    # print(str(quote["StdDateTime"]) + " " + str(priceBookDoc_LF["Value"]))

                if totalEquity_LF != None and operatingProfit_TTM != None:
                    doc_ROE_TTM = copy.deepcopy(document)
                    doc_ROE_TTM["Value"] = operatingProfit_TTM["Value"] / totalEquity_LF["Value"]
                    dataSeries_ROE_TTM.Add(doc_ROE_TTM)
                    # print(str(quote["StdDateTime"]) + " " + str(priceBookDoc_LF["Value"]))

                # --- Greenblatt Return on Capital ---
                if totalEquity_LF != None and workingCapital_LF != None and cashEquivalents_LF != None and ppe_LF != None and ebit_LYR != None:
                    doc_GreenblattROC_LYR = copy.deepcopy(document)
                    doc_GreenblattROC_LYR["Value"] = ebit_LYR["Value"] / (workingCapital_LF["Value"] - cashEquivalents_LF["Value"] + ppe_LF["Value"])
                    dataSeries_GreenblattROC_LYR.Add(doc_GreenblattROC_LYR)
                    # print(str(quote["StdDateTime"]) + " " + str(priceBookDoc_LF["Value"]))

                if totalEquity_LF != None and workingCapital_LF != None and cashEquivalents_LF != None and ppe_LF != None and ebit_TTM != None:
                    doc_GreenblattROC_TTM = copy.deepcopy(document)
                    doc_GreenblattROC_TTM["Value"] = ebit_TTM["Value"] / (workingCapital_LF["Value"] - cashEquivalents_LF["Value"] + ppe_LF["Value"])
                    dataSeries_GreenblattROC_LYR.Add(doc_GreenblattROC_LYR)
                    # print(str(quote["StdDateTime"]) + " " + str(priceBookDoc_LF["Value"]))

                # --- Gross Profit on Asset ---
                if totalAsset_LF != None and sales_LYR != None and cogs_LYR != None:
                    doc_GPA_LYR = copy.deepcopy(document)
                    doc_GPA_LYR["Value"] = (sales_LYR["Value"] - cogs_LYR["Value"]) / totalAsset_LF["Value"]
                    dataSeries_GPA_LYR.Add(doc_GPA_LYR)

                if totalAsset_LF != None and sales_TTM != None and cogs_TTM != None:
                    doc_GPA_TTM = copy.deepcopy(document)
                    doc_GPA_TTM["Value"] = (sales_TTM["Value"] - cogs_TTM["Value"]) / totalAsset_LF["Value"]
                    dataSeries_GPA_TTM.Add(doc_GPA_TTM)

                # --- Gross Profit Margin---
                if sales_LYR != None and cogs_LYR != None:
                    doc_GrossMargin_LYR = copy.deepcopy(document)
                    doc_GrossMargin_LYR["Value"] = (sales_LYR["Value"] / cogs_LYR["Value"]) - 1
                    dataSeries_GrossMargin_LYR.Add(doc_GrossMargin_LYR)

                if sales_TTM != None and cogs_TTM != None:
                    doc_GrossMargin_TTM = copy.deepcopy(document)
                    doc_GrossMargin_TTM["Value"] = (sales_TTM["Value"] / cogs_TTM["Value"]) - 1
                    dataSeries_GrossMargin_TTM.Add(doc_GrossMargin_TTM)

                # --- FCF to Asset ---
                if totalAsset_LF != None and freeCadhFlow_LYR != None:
                    doc_factor = copy.deepcopy(document)
                    doc_factor["Value"] = freeCadhFlow_LYR["Value"] / totalAsset_LF["Value"]
                    dataSeries_FreeCashFlow_LYR.Add(doc_factor)

                if totalEquity_LF != None and operatingProfit_TTM != None:
                    doc_factor = copy.deepcopy(document)
                    doc_factor["Value"] = freeCadhFlow_TTM["Value"] / totalAsset_LF["Value"]
                    dataSeries_FreeCashFlow_TTM.Add(doc_factor)


            except Exception as e:
                print(symbol + " CalcValuatioFactor::Calculation Serious Error " + " @BacktestDate " + str(dt) + " @ReportDate " + reportDate)
                print(e)
                kkwood = 0

        # ---Save to database---
        #
        for dataSeries_Factor_LYR in dataSeries_Factor_LYR_s:
            database.saveDataSeries(dataSeries_Factor_LYR)
        #
        for dataSeries_Factor_TTM in dataSeries_Factor_TTM_s:
            database.saveDataSeries(dataSeries_Factor_TTM)

        # ---Finished One Symbol---
        print("Calculated Valuation Factor::Finished to Calc Factors " + symbol + " count:" + str(
            count) + " @CurrentTime " + str(datetime.datetime.now()))
        kkwood = 0


#---Rankink by a FactorName---
def CalcProfitabilityRanking(database,instrumentByIndustry,datetime1,datetime2):
    CalcRanking(database,instrumentByIndustry,"OperatingProfit",datetime1,datetime2)
    #CalcRanking(database,instrumentByIndustry,"NetIncome",datetime1,datetime2)



